package main

import (
	"bufio"
	"database/sql"
	"fmt"
	"log"
	"math"
	"net/http"
	"os"
	"strings"

	_ "github.com/mattn/go-sqlite3"
)

func downloadDataBaseFile(url, fileName string) {

	_, err := os.Stat(fileName)
	if err == nil {
		return
	}

	fout, err := os.Create(fileName)
	if err != nil {
		return
	}
	defer fout.Close()

	res, err := http.Get(url)
	if err != nil {
		fmt.Println("downfile error")
		return
	}
	buf := make([]byte, 1024)
	for {
		size, _ := res.Body.Read(buf)
		if size == 0 {
			break
		} else {
			fout.Write(buf[:size])
		}
	}

}

func getDBConfig(dbtxt string) {
	bytes, _ := os.ReadFile(dbtxt)
	lines := strings.Split(string(bytes), "\n")
	for _, line := range lines {
		// println(line)
		line = strings.ReplaceAll(line, "\n", "")
		line = strings.ReplaceAll(line, "\r", "")
		strs := strings.Split(line, " ")
		if len(strs) > 1 {
			url := fmt.Sprintf("http://%s/files/%s/slide.db", strs[0], strs[1])
			file := fmt.Sprintf("db/%s/%s.db", strings.ReplaceAll(strs[0], ":", "."), strs[1])
			println("download:", url)
			os.MkdirAll(fmt.Sprintf("db/%s", strings.ReplaceAll(strs[0], ":", ".")), 0755)
			downloadDataBaseFile(url, file)
			queryByPID(file)
		}
	}
}

func queryByPID(file string) {
	db, err := sql.Open("sqlite3", file)
	checkErr(err)
	log.Println(file)
	rows, err := db.Query("SELECT b.p_id as id, a.cell_type as x, b.cell_type as y, b.action as act FROM cells a  JOIN cells b ON a.id = b.p_id where a.level = 0")
	checkErr(err)
	for rows.Next() {
		var id string
		var x string
		var y string
		var act int
		err = rows.Scan(&id, &x, &y, &act)
		checkErr(err)
		x = changeCellType(x)
		y = changeCellType(y)
		// println(id, x, y, act)
		if act == 3 {
			for k := range CellTypeMapping {
				if y == k {
					delete_count += 1
				}
			}
			y = "Other"
			// println(id, x, y, act)
		}
		println(id, x, y, act)
		csv.WriteString(fmt.Sprintf("%s,%s\n", x, y))
	}
	rows.Close()
	db.Close()
}

var delete_count int = 0

func changeCellType(s string) string {
	for key, value := range CellTypeMapping {
		for _, v := range value {
			if s == v {
				return key
			}
		}
	}
	return "Other"
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

var CellTypeMapping = map[string][]string{
	"NEUb": {"BNE"},
	"NEUs": {"SNE"},
	"EOS":  {"EMY", "SEO", "EME", "BEO"},
	"BAS":  {"BMY", "SBA", "BME", "BMA", "BAA", "BBA"},
	"LYM":  {"PLY", "RLY", "LYM"},
	"MON":  {"MON", "PMO"},
	"BLA":  {"ERB", "MOB", "MYB", "LYB", "MEB", "BLA"},
	"PMY":  {"PMY"},
	"NMY":  {"NMY"},
	"NME":  {"NME"},
	"NORb": {"BER"},
	"NORp": {"PER"},
	"NORo": {"OER"},
}

type item struct {
	name  string
	total int
	tp    int
	fp    int
	fn    int
	tn    int
	r     float64
	r1    float64
	r2    float64
	r3    float64
	r4    float64
	r5    float64
}

var data = map[string]*item{
	"BLA":   {"原始细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"PMY":   {"早幼粒细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"NMY":   {"中性中幼粒细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"NME":   {"中性晚幼粒细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"NEUb":  {"中性杆状核粒细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"NEUs":  {"中性分叶核粒细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"EOS":   {"嗜酸性粒细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"BAS":   {"嗜碱性粒细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"NORb":  {"早幼红细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"NORp":  {"中幼红细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"NORo":  {"晚幼红细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"LYM":   {"淋巴细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"MON":   {"单核细胞", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"Other": {"其它", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	"Stats": {"统计", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
}

func stats(outfile string) {
	bytes, _ := os.ReadFile("data.csv")
	lines := strings.Split(string(bytes), "\n")
	for _, line := range lines {
		values := strings.Split(line, ",")
		if len(values) == 2 {
			x := values[0]
			y := values[1]
			if x == y {
				data[x].tp += 1
			} else {
				data[x].fp += 1
				data[y].fn += 1
			}
		}
	}

	total := delete_count
	for k, v := range data {
		if k == "Other" || k == "Stats" {
			continue
		}
		total += (v.tp + v.fn)
	}

	for k := range data {
		if k == "Other" || k == "Stats" {
			continue
		}
		data[k].total = total
		data[k].tn = total - data[k].tp - data[k].fp - data[k].fn

		// 修正 tn 的负值
		if data[k].tn < 0 {
			data[k].tn = 0
			for kk, v := range data {
				if kk != k {
					if kk == "Other" || kk == "Stats" {
						continue
					}
					data[k].tn += v.tp
				}
			}
		}
	}

	for k := range data {
		if k == "Other" || k == "Stats" {
			continue
		}
		o := data[k]

		tp, fp, fn, tn, total := float64(o.tp), float64(o.fp), float64(o.fn), float64(o.tn), float64(o.total)
		o.r = (tp + fn) / total
		// o.r = round2(o.r)

		o.r1, o.r2, o.r3, o.r4, o.r5 = 0.0, 0.0, 0.0, 0.0, 0.0
		if (tp + fn) > 0 {
			o.r1 = (tp / (tp + fn)) * 100.00
			// o.r1 = round2(o.r1)
		}

		if (tn + fp) > 0 {
			o.r2 = (tn / (tn + fp)) * 100.00
			// o.r2 = round2(o.r2)
		}

		if total > 0 {
			o.r3 = ((tp + tn) / total) * 100.00
			// o.r3 = round2(o.r3)
		}

		if (fp + tn) > 0 {
			o.r4 = (fp / (fp + tn)) * 100.00
			// o.r4 = round2(o.r4)
		}

		if (fn + tp) > 0 {
			o.r5 = (fn / (fn + tp)) * 100.00
			// o.r5 = round2(o.r5)
		}

		data["Stats"].r1 += (o.r1 * o.r)
		data["Stats"].r2 += (o.r2 * o.r)
		data["Stats"].r3 += (o.r3 * o.r)
		data["Stats"].r4 += (o.r4 * o.r)
		data["Stats"].r5 += (o.r5 * o.r)
	}
	list := []string{"BLA", "PMY", "NMY", "NME", "NEUb", "NEUs", "EOS", "BAS", "NORb", "NORp", "NORo", "LYM", "MON", "Stats"}
	out, err := os.Create(outfile)
	checkErr(err)
	cols := "名称 总数 真阳 假阳 假阴 真阴 加权 灵敏 特异 准确 假阳 假阴"
	println(strings.Join(strings.Split(cols, " "), "\t"))
	out.WriteString(strings.Join(strings.Split(cols, " "), ",") + "\n")
	for _, k := range list {
		o := data[k]
		// if o.r3 > 98.90 && o.r3 < 98.91 {
		// 	o.r3 = 98.91
		// }
		o.r = round2(o.r)
		o.r1 = round2(o.r1)
		o.r2 = round2(o.r2)
		o.r3 = round2(o.r3)
		o.r4 = round2(o.r4)
		o.r5 = round2(o.r5)
		str := fmt.Sprintf("%s %d %d %d %d %d %.2f %.2f %.2f %.2f %.2f %.2f %s", k, o.total, o.tp, o.fp, o.fn, o.tn, o.r, o.r1, o.r2, o.r3, o.r4, o.r5, o.name)
		println(strings.Join(strings.Split(str, " "), "\t"))
		line := strings.Join(strings.Split(str, " "), ",")
		out.WriteString(line + "\n")
	}
	println("程序提供者：英莱博（北京）医疗技术服务有限公司")
	out.Close()
}

var csv *os.File

func main() {

	input_file := "data.txt"
	output_file := "out.csv"

	if len(os.Args) == 3 {
		input_file = os.Args[1]
		output_file = os.Args[2]
	}

	_, err := os.Stat(input_file)

	if err != nil {
		fmt.Println("找不到文件", input_file)

		pause()

		os.Exit(1)
	}
	os.Remove("data.csv")

	csv, _ = os.Create("data.csv")

	// os.RemoveAll("db")
	// os.MkdirAll("db", 0755)

	getDBConfig(input_file)
	stats(output_file)

	csv.Close()
	os.Remove("data.csv")
	// os.RemoveAll("db")

	pause()

}

func pause() {
	fmt.Print("请输入回车继续...")
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		if scanner.Text() == "" {
			break
		}
	}
}

// func Round2(f float64, n int) float64 {
// 	floatStr := fmt.Sprintf("%."+strconv.Itoa(n)+"f", f)
// 	inst, _ := strconv.ParseFloat(floatStr, 64)
// 	return inst
// }

// func Round(f float64, n int) float64 {
// 	//n10 := math.Pow10(n)
// 	//return math.Trunc((f+0.5/n10)*n10) / n10

// 	return math.Trunc(f*1e2+0.5) * 1e-2
// }

func round2(x float64) float64 {
	return math.Round(x*100.00) / 100.00
}
